/**
* @file
* @brief This file handles all server related functions.
*/

/*
 For now, jump to processconfigline() below and read Jerry's Task 1
 Then jump to file database.h and read Jerry's Task 2
 Note: This does not mean u have 2 different tasks, you have the same only one current task, implement processconfigline() properly
 but the numbers are just to guide you so you know the order you have to read from what I am telling u to do.

Note: I may be wrong, ask me if you feel something should be change with my methods
 and dont just implement what i tell you, understand why and let me know if you dont get what I am saying
 cause when i code stuff that depends on what you're doing, i'll assume that you'll implement exactly as i told you too
Note: Don't delete any of my comments for you, once you're done, I'll need to refer to them
 to make sure you accounted for all the cases i told you about, i'll delete these comments as I confirm they are implemented
Jerry Task 1: server.c
Jerry Task 2: database.h
Jerry Task 3: utils.c
@note: These are not 3 tasks, it is just one taks which is implement processing the new configuration file.
*/


//-------------------------------------------------------
// Header Files
//-------------------------------------------------------
#include "server.h" // for client structure
#include <stdio.h>
#include <stdlib.h>
// Flex/ Bison parsing occurs in utils.c where it includes the proper header files compiled properly in Makefile

//-------------------------------------------------------
// Constants
//-------------------------------------------------------
#define MAX_LISTENQUEUELEN 20   // The maximum number of queued connections.
#define LOGGING 1
    ///< 0 -> disabled logging,
    ///< 1 -> log to stdout,
    ///< 2 -> log to file, that follows format: Client/Server-yyyy-mm-dd-hh-ss.log
//-------------------------------------------------------
// Function Prototypes
//-------------------------------------------------------


//-------------------------------------------------------
// Main
//-------------------------------------------------------
/**
* Main function that parses in commandline arguments and initializes the server
* @param argv[1] configurationFile
*/
int main(int argc, char *argv[])
{
/* TEMP FOR DEBUGGING
    addclient(20);
    printf("Top fd is %d\n", top->fd);
*/
/*
// These are in storage.c for M2, need to change for M4 when multi client
    int authen = 0; // single client server not authenticated
    int connected = 0; // single client server not connected
*/

    int totalProcTime = 0;	// total Processing Time
    struct timeval start_time, end_time;	//Same as client.c implementation
    int tempT = 0;
    char timeOut[64];
    // Process command line arguments.
    // This program expects exactly one argument: the config file name.
    assert(argc > 0); // aborts program if this is false
    if (argc != 2)
    {
	printf("Usage %s <config_file>\n", argv[0]);
	exit(EXIT_FAILURE);
    }
    // Configuration file
    char *config_file = argv[1];
    // Read the config_file and store into the config_params struct
    struct config_params params; // structure to marshall and store configuration parameters into a structure
    struct check_duplmis check; // structure to check duplicates of configuration parameters
    initialCheck(&check);
    // @note Table is currently defined in server.c only
    // Initialize table as a sorted array
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////initializeSortedArrayTable();
    // @note CTRL+C automatically frees all memory according to T.A
    // Read the configuration file and check to make sure it meets the configuration file requirements

/*
Jerry's Task 1 Begin:
Modify read_config below that is in utils.h and defined in utils.c
such that it is able to accept the new table line structure

Look at Milestone 3 for examples
In short,
table format will be:
table nameOfTable col1Name:Type, col2Name:Type, ... , colXName:Type
// Note: X <= MAX_COLUMNS_PER_TABLE
// Note: Type can be a char[SIZE] which means when you create a name for this column,
// you need to create char name[SIZE] and make sure your input for later insertions does not exceed SIZE-1
Jerry's Task 1 END
*/
// THIS ENTIRE FUNC. HAS TO BE CHANGED TO CONFORM TO YACC & LEX
    int status = read_config(config_file, &params, &check); // returns 0 if successful
    if (status != 0)
    {
	printf("Error processing config file.\n");
	exit(EXIT_FAILURE);
    }
    // Sort the tables
    /////////////////////////////////////////////////////////////////////////sortTable(); // TODO: Change this to merge/quick sort instead of bubble sort
printf("STARTING TO PRINT TABLE NAME:\n");
    printTableName();
    // Set up time for logging
    char fileName[MAX_STRTYPE_SIZE];
    time_t basetime;
    struct tm * conTime;
    basetime = time(NULL);
    conTime = localtime(&basetime);
    strftime(fileName, sizeof(fileName), "Server-%Y-%m-%d-%H-%M-%S.log",conTime);
    int logChoice = LOGGING;
    // Initialize fileServer based on LOGGING
    switch(logChoice)
    {
	case 0: // Disable Logging
	    fileServer = NULL;
	    break;
	case 1: // Log to stdout
	    fileServer = stdout;
	    break;
	case 2: // Log to File
	    // Append file for appending an update
	    fileServer = fopen (fileName,"a+");
	    if (fileServer == NULL)
	    {
		printf("Error opening file.\n");
		fclose(fileServer);
		exit(EXIT_FAILURE);
	    }
	    break;
    }
    // Server on localhost:1376
    sprintf(message, "Server on %s:%d\n", params.server_host, params.server_port);
    logger (fileServer, message);
    // Create a socket for the server
    // listensock is the sockfd to be listening to
    int listensock = socket(PF_INET, SOCK_STREAM, 0);
    if (listensock < 0)
    {
	printf("Error creating socket.\n");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Allow listening port to be reused if defunctional
    int yes = 1;
    status = setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    if (status != 0)
    {
	printf("Error configuring socket.\n");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Bind to the listening port.
    struct sockaddr_in listenaddr;
    memset(&listenaddr, 0, sizeof listenaddr);
    listenaddr.sin_family = AF_INET;
    listenaddr.sin_port = htons(params.server_port);
    inet_pton(AF_INET, params.server_host, &(listenaddr.sin_addr)); // bind to local IP address
    // Let server bind to this socket
    status = bind(listensock, (struct sockaddr*) &listenaddr, sizeof listenaddr);
    if (status != 0)
    {
	printf("Error binding socket.");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Start listening for connections.
    status = listen(listensock, MAX_LISTENQUEUELEN);
    if (status != 0)
    {
	printf("Error listening on socket.\n");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Listen loop.
    int wait_for_connections = 1;
    // Infinite while loop for milestone 1
    while (wait_for_connections)
    {
	// Wait for a connection.
	struct sockaddr_in clientaddr;
	socklen_t clientaddrlen = sizeof clientaddr;
	// Accept any calls from any client
	// @note: Will just poll till a client is accepted
	// @note: Right now only works with 1 clientsock
	int clientsock = accept(listensock, (struct sockaddr*)&clientaddr, &clientaddrlen);
	// Add this client to linked list of clients
	addclient(clientsock);
	// Accepted client wil return it's file descriptor as clientsock
	if (clientsock < 0)
	{
	    printf("Error accepting a connection.");
	    Fclose(logChoice, 1);
	    exit(EXIT_FAILURE);
	}
	sprintf(message, "Got a connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
	// TODO: For Milestone 3, change code to work with > 1 client
	// Get commands from client.
	int wait_for_commands = 1;
	do
	{
	    // Read a line from the client.
	    char cmd[MAX_CMD_LEN];
	    // Read from clientsock's file descriptor to command
	    int status = recvline(clientsock, cmd, MAX_CMD_LEN);
	    if (status != 0)
	    {
		// Here, either an error occurred or the client closed the connection.
		// Stop waiting for commands
		wait_for_commands = 0;
	    }
	    else
	    {
		// Handle the command from the client.
		// @note: For milestone1, this just prints it back out to server
		//********** TESTING ONLY
		//**********/

		int status = handle_command(clientsock, cmd, &params);
		// If handled command fails, stop waiting for command
		if (status != 0)
		{
		    printf("Error: Handle command return status of -1 to main() in server");
		    wait_for_commands = 0; // stop waiting for commands
		    exit(EXIT_FAILURE); // @note original code for M1 by prof. was to just EXIT_SUCCESS at bottom
		}
            }
         }
	while (wait_for_commands); //do while client is still interacting
        // Close the connection with the client.
//	close(clientsock);
	sprintf(message, "Closed connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
    }
    // Stop listening for connections.
    close(listensock);
    return EXIT_SUCCESS;
}

//-------------------------------------------------------
// Helper Functions
//-------------------------------------------------------
/**
* This function handles a command given by sending it as a string to a socket given.
* @param sock The socket descriptor to output commands to
* @param cmd A string to be sent to a socket descriptor
* @returns 0 if success, returns error code if failure
*/
int handle_command(int sock, char *cmd, struct config_params *params)
{
    char table[MAX_TABLE_LEN] = "a", key[MAX_KEY_LEN+1] = "a", username[MAX_USERNAME_LEN+1] = "a", password[MAX_ENC_PASSWORD_LEN+1] = "a";
    struct storage_record stTemp;
    char value[sizeof stTemp.value];
    char* args[4];
	char data[100];
    // Get arguments into args and numArgs holds number of arguments given whic is 3 or 4.
    // @note: Need to free all args once done
    int numArgs = getArguments(args, cmd);
    // Note: Must free all args later
    int i = 0;
    sprintf(message, "Processing command '%s'\n", cmd);
    logger(fileServer, message);
    // @note: sock is the socket file descriptor (sockfd)
    // Send the command out to sockfd, which is the server
    // and end with a new line
// printf("arguments receive are:\n%s %s %s %s\n", args[0], args[1], args[2], args[3]);
    // Handle Commands
    //--------------------------------------------
    // GET table key (NULL)
    //--------------------------------------------
    if (strcmp(args[0], "GET") == 0)
    {
	if(findTableName(args[1]) == 0)
	{
            sprintf(message, "-2\n"); // 1=> success
            sendall(sock, message, strlen(message));
	    return 0; // server shouldnt crash if client fails
	}

	int getStat = getValue(args[1], args[2], data); // get the value on args[3] 
	switch (getStat)
	{
	    case -1:
		sprintf(message, "-1\n");
		sendall(sock, message, strlen(message));
		return 0; // server shouldn't crash if client fails 
		break;
	    case 0:
		sprintf(message, "0\n");
		sendall(sock, message, strlen(message));
		return 0; // server shouldn't crash if client fails 
		break;
	    case 1:
		// Send success and actual message together
		sprintf(message, "1@%s\n", data);
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    default: // should never be here
		break;
	}
    }

    //--------------------------------------------
    // SET table key value
    //--------------------------------------------
    if (strcmp(args[0], "SET") == 0)
    {
	if(findTableName(args[1]) == 0)
	{
            sprintf(message, "-2\n"); // 1=> success
            sendall(sock, message, strlen(message));
	    return 0;	// Server should not crash regardless 
	}
	// If given NULL string as input, args 3 should be NULL
	if (strcmp(args[3], "NULL") == 0 || strcmp(args[3], "") == 0)
	{
	   args[3] = NULL;
	}
	int insertStat;
	int i = 0;
	char** colParam;
	char** colName;
	char** colType;
	int numCol;
	char* tempkey = "InitialKey";
	getValue(args[1], tempkey, data); // Get data in InitialKey which is entry containing config data

	numCol = getCol(data);	// Number of columns
	colParam = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colParam[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+11));
	}
	colName = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colName[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+1));
	}
	colType = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colType[i] = (char*) malloc(sizeof(char*) * 11);
	}
	getArgumentsJer(colParam, data);	// Get column data
	separateParam(colParam, colName, colType, numCol);	// Separate column data
	if(args[3]==NULL||args[3]=="NULL")
		insertStat = insertValue(args[1], args[2], args[3]);
	else if(validParam(colName, args[3], colType, numCol))	//	Is it valid parameters?
	{
		insertStat = insertValue(args[1], args[2], args[3]);
		i = strlen(args[3]);
		if(i > 799)
			i = -3;		//	Value too long
	}
	else
		insertStat = -3;

	for(i = 0; i < numCol; i++)	//	Free colParam
	{
		free(colParam[i]);

	}
	free(colParam);
	colParam=NULL;
	for(i = 0; i < numCol; i++)	//	Free colName
	{
		free(colName[i]);
		colName[i]=NULL;
	}
	free(colName);
	colName=NULL;
	for(i = 0; i < numCol; i++)					//	Free colType
	{
		free(colType[i]);
		colType[i]=NULL;
	}
	free(colType);
	colType=NULL;
	switch (insertStat)
	{
		case -3:
		sprintf(message, "-3\n");
		sendall(sock, message, strlen(message));
		return 0; // Server shouldnt crash regardless
	    case -1:
		sprintf(message, "-1\n");
		sendall(sock, message, strlen(message));
		return 0; // server shouldnt crash regardless
		break;
	    case 1:
		sprintf(message, "1\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    case 2:
		sprintf(message, "2\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    case 3:
		sprintf(message, "3\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    default:
		break;
	}
    }
    //--------------------------------------------
    // AUTH username encrptpassword (NULL)
    //--------------------------------------------
    if (strcmp(args[0], "AUTH") == 0)
    {
	if (strcmp(args[1], params->username) == 0)
	{
	    if(strcmp(args[2], params->password) == 0)
	    {
		struct client* newclient;
//TODO: account for case where client not connected, make a check_connect() and check_auth() function
		newclient = getclient(sock);
		strcpy(newclient->username, params->username);
		strcpy(newclient->cryptpasswd, params->password);
		newclient->auth = true;
		sprintf(message, "1\n"); // 1=> success
		sendall(sock, message, strlen(message));
		return 0;
	    }
	}
	sprintf(message, "0\n");
	sendall(sock, message, strlen(message));
	return 0;
    }
	//--------------------------------------------
    // QUERY table predicates max_keys
    //--------------------------------------------
    if (strcmp(args[0], "QUE") == 0)
    {
	
	if(findTableName(args[1]) == 0)
	{
            sprintf(message, "-2\n"); // 1=> success
            sendall(sock, message, strlen(message));
	    return 0; // server must not crash regardless
	}
	int i = 0;
	char** predName;
	char* oper;
	char** predValue;
	char** colParam;
	char** colName;
	char** colType;
	char** keyOut;
	int numCol;
	int max_keys;
	char* tempkey = "InitialKey";
	int noError=1;
	int numKeysOut=0;
	int totalRecords=0;

	
//--------------------------------------------------------------------
//This portion gets the config data from the data structure
	getValue(args[1], tempkey, data); // Get data in InitialKey which is entry containing config data

	numCol = getCol(data);	// Number of columns
	
	colParam = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colParam[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+11));
	}
	
	colName = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colName[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+1));
	}
	
	colType = (char**) malloc(sizeof(char**) * numCol);	// Initialize an array to store each column name
	for(i = 0; i < numCol; i++)
	{
		colType[i] = (char*) malloc(sizeof(char*) * 11);
	}
	
	getArgumentsJer(colParam, data);	// Get column data
	
	separateParam(colParam, colName, colType, numCol);	// Separate column data
	
//-----------------------------------------------------------------------------
//This for initializing and parsing from predicate string
	
	predName = (char**) malloc(sizeof(char**) * MAX_COLUMNS_PER_TABLE);	// Initialize an array to store each column name
	for(i = 0; i < MAX_COLUMNS_PER_TABLE; i++)
	{
		predName[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+1));
	}
	
	oper = (char*) malloc(sizeof(char*) * (MAX_COLUMNS_PER_TABLE+1));	// Initialize an array to store each column name
	
	predValue = (char**) malloc(sizeof(char**) * MAX_COLUMNS_PER_TABLE);	// Initialize an array to store each column name
	for(i = 0; i < 10; i++)
	{
		predValue[i] = (char*) malloc(sizeof(char*) * (MAX_VALUE_LEN+1));
	}

	max_keys = atoi(args[3]);
	keyOut = (char**) malloc(sizeof(char**) * max_keys);	// Initialize an array to store each column name
	for(i = 0; i < max_keys; i++)
	{
		keyOut[i] = (char*) malloc(sizeof(char*) * (MAX_COLNAME_LEN+1));
	}
	
	if(args[2]!="NULL")
	{
	noError = getPred(args[2], predName, oper, predValue);
	noError = checkPredDuplicate(predName, oper);
	
	if(noError==1)
	{
		noError = validPredName(colName, colType, numCol, predName, predValue, oper);
	}
	
	
	if(noError)
	{
		totalRecords = findKeys(predName, predValue, oper, args[1], keyOut, max_keys, numCol);
		numKeysOut = findKeysCopiedNum(predName, predValue, oper, args[1], keyOut, max_keys, numCol);
	}
	}
	else
	{
		noError = 1;
		totalRecords = getAllKeys(args[1], keyOut, max_keys);
		numKeysOut = getAllKeysCopiedNum(args[1], keyOut, max_keys);
	}
	
//-----------------------------------------------------------------------
//This section deals with freeing memory	
	for(i = 0; i < MAX_COLUMNS_PER_TABLE; i++)	//	Free predName
	{
		free(predName[i]);

	}
	free(predName);
	predName=NULL;
		
	free(oper);
	oper=NULL;
	
	for(i = 0; i < MAX_COLUMNS_PER_TABLE; i++)					//	Free predValue
	{
		free(predValue[i]);
		predValue[i]=NULL;
	}
	free(predValue);
	predValue=NULL;
	
	for(i = 0; i < numCol; i++)	//	Free colParam
	{
		free(colParam[i]);

	}
	free(colParam);
	colParam=NULL;			
		
	for(i = 0; i < numCol; i++)	//	Free colName
	{
		free(colName[i]);
		colName[i]=NULL;
	}
	free(colName);
	colName=NULL;
	
	for(i = 0; i < numCol; i++)					//	Free colType
	{
		free(colType[i]);
		colType[i]=NULL;
	}
	free(colType);
	colType=NULL;
//----------------------------------------------------------------------------
	
	switch (noError)
	{
	    case 0:
		sprintf(message, "-3\n");
		sendall(sock, message, strlen(message));
		return 0; // server must not exit due to error from client
		break;
	    case 1:
		// Send success and actual message together
		sprintf(message, "%d", totalRecords);
		for(i = 0; i < numKeysOut; i++)
		{
			strcat(message, "@");
			strcat(message, keyOut[i]);
		}
		strcat(message, "\n");
		
		for(i = 0; i < max_keys; i++)	//	Free colName
		{
			free(keyOut[i]);
		}
		free(keyOut);
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    default: // should never be here
		break;
	}
    }

    // @note free(args[i]) here will crash, not sure if need to free
    return 0;
}
